/**
 * Copyright (c) 2015-2017, Henry Yang 杨勇 (gismail@foxmail.com).
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lambkit.dao.model;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.jfinal.plugin.activerecord.*;
import com.lambkit.db.sql.Columns;
import com.lambkit.db.sql.Example;
import com.lambkit.plugin.activerecord.dialect.LambkitDialect;

import java.lang.reflect.ParameterizedType;
import java.util.List;

/**
 * 实现LambkitService抽象类
 */
public abstract class ModelServiceImpl<M extends Model<M>> implements LambkitModelService<M> {

	public abstract M dao();

	public abstract String configName();

	public abstract String getTableName();

	public M findById(Object idValue) {
		return dao().findById(idValue);
	}

	@Override
	public M findByPrimaryKey(Object id) {
		return dao().findById(id);
	}

	@Override
	public M findFirst(Example example) {

		return dao().findFirst(exampleToSqlPara(example, null));
	}

	@Override
	public M findFirst(Columns columns) {

		return findFirst(Example.create(getTableName(), columns));
	}

	@Override
	public M findFirst(Columns columns, String orderby) {

		return findFirst(Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public M findFirstByColumns(Columns columns) {

		return findFirst(Example.create(getTableName(), columns));
	}

	@Override
	public M findFirstByColumns(Columns columns, String orderby) {

		return findFirst(Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public List<M> find(Example example) {

		return dao().find(exampleToSqlPara(example, null));
	}

	@Override
	public List<M> find(Columns columns) {

		return find(Example.create(getTableName(), columns));
	}

	@Override
	public List<M> find(Columns columns, String orderby) {

		return find(Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public List<M> findListByColumns(Columns columns) {

		return find(Example.create(getTableName(), columns));
	}

	@Override
	public List<M> findListByColumns(Columns columns, String orderby) {

		return find(Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public List<M> find(Example example, Integer count) {

		return dao().find(exampleToSqlPara(example, count));
	}

	@Override
	public List<M> find(Columns columns, Integer count) {

		return dao().find(exampleToSqlPara(Example.create(getTableName(), columns), count));
	}

	@Override
	public List<M> find(Columns columns, String orderby, Integer count) {

		return dao().find(exampleToSqlPara(Example.create(getTableName(), columns).setOrderBy(orderby), count));
	}

	@Override
	public List<M> findListByColumns(Columns columns, Integer count) {

		return dao().find(exampleToSqlPara(Example.create(getTableName(), columns), count));
	}

	@Override
	public List<M> findListByColumns(Columns columns, String orderby, Integer count) {

		return dao().find(exampleToSqlPara(Example.create(getTableName(), columns).setOrderBy(orderby), count));
	}

	@Override
	public Page<M> paginate(Integer pageNumber, Integer pageSize, Example example) {

		return dao().paginate(pageNumber, pageSize, exampleToSqlParaForPaginate(example));
	}

	@Override
	public Page<M> paginate(Integer pageNumber, Integer pageSize, Columns columns) {

		return dao().paginate(pageNumber, pageSize,
				exampleToSqlParaForPaginate(Example.create(getTableName(), columns)));
	}

	@Override
	public Page<M> paginate(Integer pageNumber, Integer pageSize, Columns columns, String orderby) {

		return paginate(pageNumber, pageSize, Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public Page<M> paginateByColumns(Integer pageNumber, Integer pageSize, Columns columns) {

		return paginate(pageNumber, pageSize, Example.create(getTableName(), columns));
	}

	@Override
	public Page<M> paginateByColumns(Integer pageNumber, Integer pageSize, Columns columns, String orderby) {

		return paginate(pageNumber, pageSize, Example.create(getTableName(), columns).setOrderBy(orderby));
	}

	@Override
	public Page<M> paginate(Example example, Integer offset, Integer limit) {

		int pageSize = limit;
		int pageNumber = offset / pageSize + 1;
		return paginate(pageNumber, pageSize, example);
	}

	@Override
	public Page<M> paginate(Columns columns, Integer offset, Integer limit) {

		int pageSize = limit;
		int pageNumber = offset / pageSize + 1;
		return paginate(pageNumber, pageSize, Example.create(getTableName(), columns));
	}

	@Override
	public Page<M> paginate(Columns columns, String orderby, Integer offset, Integer limit) {

		return paginate(Example.create(getTableName(), columns).setOrderBy(orderby), offset, limit);
	}

	@Override
	public Page<M> paginateByColumns(Columns columns, Integer offset, Integer limit) {

		return paginate(Example.create(getTableName(), columns), offset, limit);
	}

	@Override
	public Page<M> paginateByColumns(Columns columns, String orderby, Integer offset, Integer limit) {

		return paginate(Example.create(getTableName(), columns).setOrderBy(orderby), offset, limit);
	}

	public Long count(Example example) {
		example.setSelectSql(" count(*) ");
		SqlPara sqlPara = exampleToSqlPara(example, null);
		DbPro dp;
		if (StrUtil.isBlank(configName())) {
			dp = Db.use();
		} else {
			dp = Db.use(configName());
		}
		if (sqlPara.getPara() == null) {
			return dp.queryLong(sqlPara.getSql());
		}
		return dp.queryLong(sqlPara.getSql(), sqlPara.getPara());
	}

	public Long count(Columns columns) {
		return count(Example.create(getTableName(), columns));
	}

	public boolean insert(Record record) {
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).save(getTableName(), record);
		} else {
			return Db.save(getTableName(), record);
		}
	}

	public boolean insert(String primaryKey, Record record) {
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).save(getTableName(), primaryKey, record);
		} else {
			return Db.save(getTableName(), primaryKey, record);
		}
	}

	public boolean deleteById(Object idValue) {
		return dao().deleteById(idValue);
	}

	public int deleteByPrimaryKey(Object id) {
		return dao().deleteById(id) ? 1 : -1;
	}

	public int deleteByPrimaryKeys(String ids) {
		if (StrUtil.isBlank(ids)) {
			return 0;
		}
		String[] idArray = ids.split(",");
		int count = 0;
		for (String idStr : idArray) {
			if (StrUtil.isBlank(idStr)) {
				continue;
			}
			Long id = Long.parseLong(idStr);
			int result = deleteByPrimaryKey(id);
			count += result;
		}
		return count;
	}

	@Override
	public int delete(Example example) {
		SqlPara sqlPara = exampleToDeleteSqlPara(example);
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).delete(sqlPara.getSql(), sqlPara.getPara());
		} else {
			return Db.delete(sqlPara.getSql(), sqlPara.getPara());
		}
	}

	@Override
	public int delete(Columns columns) {
		return delete(Example.create(getTableName(), columns));
	}

	@Override
	public int update(Record record, Example example) {
		SqlPara sqlPara = getDialect().forUpdateByExample(record, example);
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).update(sqlPara);
		} else {
			return Db.update(sqlPara);
		}
	}

	@Override
	public int update(Record record, Columns columns) {

		return update(record, Example.create(getTableName(), columns));
	}

	@Override
	public boolean update(Record record) {
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).update(getTableName(), record);
		} else {
			return Db.update(getTableName(), record);
		}
	}

	@Override
	public boolean updateByPrimaryKey(String primaryKey, Record record) {
		if (StrUtil.isNotBlank(configName())) {
			return Db.use(configName()).update(getTableName(), primaryKey, record);
		} else {
			return Db.update(getTableName(), primaryKey, record);
		}
	};

	public void join(Page<? extends Model> page, String joinOnField) {
		join(page.getList(), joinOnField);
	}

	public void join(Page<? extends Model> page, String joinOnField, String[] attrs) {
		join(page.getList(), joinOnField, attrs);
	}

	public void join(List<? extends Model> models, String joinOnField) {
		if (CollUtil.isNotEmpty(models)) {
			for (Model m : models) {
				join(m, joinOnField);
			}
		}
	}

	public void join(List<? extends Model> models, String joinOnField, String[] attrs) {
		if (CollUtil.isNotEmpty(models)) {
			for (Model m : models) {
				join(m, joinOnField, attrs);
			}
		}
	}

	public void join(Page<? extends Model> page, String joinOnField, String joinName) {
		join(page.getList(), joinOnField, joinName);
	}

	public void join(List<? extends Model> models, String joinOnField, String joinName) {
		if (CollUtil.isNotEmpty(models)) {
			for (Model m : models) {
				join(m, joinOnField, joinName);
			}
		}
	}

	public void join(Page<? extends Model> page, String joinOnField, String joinName, String[] attrs) {
		join(page.getList(), joinOnField, joinName, attrs);
	}

	public void join(List<? extends Model> models, String joinOnField, String joinName, String[] attrs) {
		if (CollUtil.isNotEmpty(models)) {
			for (Model m : models) {
				join(m, joinOnField, joinName, attrs);
			}
		}
	}

	/**
	 * 添加关联数据到某个model中去，避免关联查询，提高性能。
	 *
	 * @param model       要添加到的model
	 * @param joinOnField model对于的关联字段
	 */
	public void join(Model model, String joinOnField) {
		if (model == null) {
			return;
		}
		String id = model.getStr(joinOnField);
		if (id == null) {
			return;
		}
		Model m = findById(id);
		if (m != null) {
			model.put(StrUtil.lowerFirst(m.getClass().getSimpleName()), m);
		}
	}

	/**
	 * 添加关联数据到某个model中去，避免关联查询，提高性能。
	 *
	 * @param model
	 * @param joinOnField
	 * @param attrs
	 */
	public void join(Model model, String joinOnField, String[] attrs) {
		if (model == null) {
			return;
		}
		String id = model.getStr(joinOnField);
		if (id == null) {
			return;
		}
		Model m = findById(id);
		if (m != null) {
			m = copy(m);
			m.keep(attrs);
			model.put(StrUtil.lowerFirst(m.getClass().getSimpleName()), m);
		}
	}

	/**
	 * 添加关联数据到某个model中去，避免关联查询，提高性能。
	 *
	 * @param model
	 * @param joinOnField
	 * @param joinName
	 */
	public void join(Model model, String joinOnField, String joinName) {
		if (model == null) {
			return;
		}
		String id = model.getStr(joinOnField);
		if (id == null) {
			return;
		}
		Model m = findById(id);
		if (m != null) {
			model.put(joinName, m);
		}
	}

	/**
	 * 添加关联数据到某个model中去，避免关联查询，提高性能。
	 *
	 * @param model
	 * @param joinOnField
	 * @param joinName
	 * @param attrs
	 */
	public void join(Model model, String joinOnField, String joinName, String[] attrs) {
		if (model == null)
			return;
		String id = model.getStr(joinOnField);
		if (id == null) {
			return;
		}
		Model m = findById(id);
		if (m != null) {
			m = copy(m);
			m.keep(attrs);
			model.put(joinName, m);
		}

	}

	public void keep(Model model, String... attrs) {
		if (model == null) {
			return;
		}

		model.keep(attrs);
	}

	public void keep(List<? extends Model> models, String... attrs) {
		if (CollUtil.isNotEmpty(models)) {
			for (Model m : models) {
				keep(m, attrs);
			}
		}
	}

	public Model copy(Model model) {
		Model m = null;
		try {
			m = getModelClass().newInstance();
			m._setAttrs(model);
		} catch (Throwable e) {
			e.printStackTrace();
		}
		return m;
	}

	/**
	 * 获取类泛型class
	 * 
	 * @return
	 */
	public Class<M> getModelClass() {
		return (Class<M>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	private SqlPara exampleToDeleteSqlPara(Example example) {
		return getDialect().forDeleteByExample(example);
	}

	private SqlPara exampleToSqlPara(Example example, Object limit) {
		return getDialect().forFindByExample(example, limit);
	}

	private SqlPara exampleToSqlParaForPaginate(Example example) {
		return getDialect().forPaginateByExample(example);
	}

	private LambkitDialect getDialect() {
		if (StrUtil.isNotBlank(configName())) {
			return (LambkitDialect) DbKit.getConfig(configName()).getDialect();
		} else {
			return (LambkitDialect) DbKit.getConfig().getDialect();
		}
	}
}
